
#define DUA_CORE

#include "dua_def.h"
#include "dua_lex.h"
#include "dua_opcode.h"
#include "dua_proto.h"

typedef struct {
	DuaObj obj;
	uint32_t* code;
	unsigned codeCap;
	unsigned codeCount;
	DuaValue* k;
	unsigned kCount;
	unsigned kCap;
}LoadCtx;

static int TK_CODES[][2] = {
	{TK_LOAD, OP_LOAD},
	{TK_MOVE, OP_MOVE},
	{TK_POP, OP_POP},
	{TK_COPY, OP_COPY},
	{TK_CASTB, OP_CASTB},
	{TK_CASTI, OP_CASTI},
	{TK_CASTL, OP_CASTL},
	{TK_CASTF, OP_CASTF},
	{TK_CASTD, OP_CASTD},
	{TK_ADDI, OP_ADDI},
	{TK_SUBI, OP_SUBI},
	{TK_MULI, OP_MULI},
	{TK_DIVI, OP_DIVI},

	{TK_END, OP_END},
	{0, 0},
};

static inline void
lexError(DuaState* D, DuaLex* lex, const char* msg) {
	char buf[128];
	int len = snprintf(buf, 128, "ctx lex error: line(%d), msg(%s)",
		lex->line, msg);
	duaRet(D, DUA_ERROR, buf);
}

static inline int64_t
checkInteger(DuaState* D, DuaLex* lex) {
    if(lex->token.tk != TK_NUMBER){
        lexError(D, lex, "expect integer");
    }else if(lex->token.sem.type != DUA_TLONG){
		lexError(D, lex, "expect integer");
    }
	return lex->token.sem.l;
}

static inline double
checkNumber(DuaState* D, DuaLex* lex) {
	if (lex->token.tk != TK_NUMBER) {
		lexError(D, lex, "expect number");
	}
	return lex->token.sem.d;
}

static void
pushk(DuaState* D, LoadCtx* ctx, DuaLex* lex) {
	duaNextLex(D, lex);
	DuaToken* tk = &lex->token;
	DuaValue sem = { 0 };
	switch (tk->tk) {
	case TK_BOOL:
		duaNextLex(D, lex);
		sem = tk->sem;
		sem.type = DUA_TBOOL;
		break;
	case TK_INT:
		duaNextLex(D, lex);
		sem.i = (int32_t)checkInteger(D, lex);
		sem.type = DUA_TINT;
		break;
	case TK_LONG:
		duaNextLex(D, lex);
		sem.l = (int64_t)checkInteger(D, lex);
		sem.type = DUA_TLONG;
		break;
	case TK_FLOAT:
		duaNextLex(D, lex);
		sem.f = (float)checkNumber(D, lex);
		sem.type = DUA_TFLOAT;
		break;
	case TK_DOUBLE:
		duaNextLex(D, lex);
		sem.d = checkNumber(D, lex);
		sem.type = DUA_TDOUBLE;
		break;
	case TK_TSTRING:
		duaNextLex(D, lex);
		if (tk->tk != TK_STR) {
			lexError(D, lex, "except string");
		}
		sem = tk->sem;
		break;
	default:
		lexError(D, lex, "except type name");
		break;
	}
	if (ctx->kCount >= ctx->kCap) {
		unsigned cap = ctx->kCap * 2;
		ctx->k = duaRelloc(D, ctx->k,
			sizeof(DuaValue) * ctx->kCap, sizeof(DuaValue) * cap);
	}
	ctx->k[ctx->kCount++] = sem;
	duaNextLex(D, lex);
}

static void
pushCode(DuaState* D, LoadCtx* ctx, DuaLex* lex) {
	duaNextLex(D, lex);
	DuaToken* tk = &lex->token;
	int i = 0;
	int32_t op = -1;
	while (TK_CODES[i][0]) {
		if(TK_CODES[i][0] == tk->tk){
			op = TK_CODES[i][1];
			break;
		}
		i++;
	}
	if (op == -1) {
		lexError(D, lex, "unknow opcode token");
	}
	int32_t r1, r2, flag;
	duaNextLex(D, lex);
	r1 = (int32_t)checkInteger(D, lex);
	duaNextLex(D, lex);
	r2 = (int32_t)checkInteger(D, lex);
	duaNextLex(D, lex);
	flag = (int32_t)checkInteger(D, lex);
	duaNextLex(D, lex);

	uint32_t code = duaMakeCode(op, r1, r2, flag);
	if (ctx->codeCount >= ctx->codeCap) {
		unsigned cap = ctx->codeCap * 2;
		ctx->code = duaRelloc(D, ctx->code,
			sizeof(uint32_t) * ctx->codeCap, sizeof(uint32_t) * cap);
	}
	ctx->code[ctx->codeCount++] = code;
}


static void
freeProto(DuaObj* obj) {
}

extern DuaProto*
duaNewProto(DuaState* D, unsigned codeCount, unsigned kCount) {
	DuaProto* proto = duaNewObj(D, 0, sizeof(DuaProto));
	proto->stackCount = 0;
	proto->codeCount = 0;
	proto->kCount = kCount;
	proto->code = malloc(sizeof(uint32_t) * codeCount);
	proto->k = malloc(sizeof(DuaValue) * kCount);
	proto->obj.free = freeProto;
	return proto;
}

extern DuaProto*
duaLoadProto(DuaState* D, const char* str, size_t len) {
	LoadCtx* ctx = duaNewObj(D, 0, sizeof(LoadCtx));
	ctx->codeCount = 0;
	ctx->codeCap = 128;
	ctx->code = duaMalloc(D, sizeof(uint32_t) * 128);
	ctx->kCount = 0;
	ctx->kCap = 16;
	ctx->k = duaMalloc(D, sizeof(DuaValue) * 16);

	DuaLex* lex = duaNewLex(D, duaNewStringLen(D, str, len));
	duaNextLex(D, lex);
	DuaToken* tk = &lex->token;
	while (tk->tk != TK_EOS) {
		switch (tk->tk) {
		case '=':
			pushk(D, ctx, lex);
			break;
		case '.':
			pushCode(D, ctx, lex);
			break;
		default:
			lexError(D, lex, "unknow token");
			break;
		}
	}
	DuaProto* proto = duaNewProto(D, ctx->codeCount, ctx->kCount);
	memcpy(proto->code, ctx->code, sizeof(uint32_t) * ctx->codeCount);
	memcpy(proto->k, ctx->k, sizeof(DuaValue) * ctx->kCount);
	proto->stackCount = 32;
	return proto;
}
